﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;

namespace NyanBankIntfTool
{
    /// <summary>
    /// 数据分析类
    /// </summary>
    class NewBankIntfParser
    {
        private readonly ProcessBarController processBarController;
        private DataSet dbfData;
        private Dictionary<string, IniParser> configTemplate; //配置表
        private Dictionary<string, DataSet> parsedData; //分析后的数据

        //private const string RegexRelationString = @"(.*)\.(.*)\|(.*)\((.*)\)Spliter\((.*)\)\|Spliter\((.*)\)";
        private const string NullString = "null";

        private IniParser dictionary; //数据字典
        private IniParser fileList; //文件列表
        private string fileListName; //文件列表名
        private const string DictFileName = @".\Config\Dictionary.ini"; //字典文件路径
        private const string FileListFileName = @".\Config\FileList.ini"; //文件列表文件路径
        private const string TemplateSafeFileNameCmds = @".\Config\CMDS验收模板.xlsx"; //模板文件名
        private const string TemplateSafeFileNameSelf = @".\Config\本方成交.xlsx"; //模板文件名
        private const string TemplateSafeFileNameAffiliates = @".\Config\关联机构成交.xlsx"; //模板文件名

        private DateTime beginBusinDateTime; //业务验收日期+开始时间
        private DateTime endBusinDateTime; //业务验收日期+结束时间

        private DateTime beginStableDateTime; //稳定性验收日期
        private DateTime endStableDateTime; //稳定性验收天数

        private DateTime beginNonBusinDateTime; //非业务验收日期
        private DateTime endNonBusinDateTime; //非业务验收天数

        public string DbfPath { get; set; } //DBF文件路径
        public string OutputSafeFileNameCmds { get; set; } //输出文件名
        public string OutputSafeFileNameSelf { get; set; } //输出文件名
        public string OutputSafeFileNameAffiliates { get; set; } //输出文件名
        public string OutputPath { get; set; } //输出路径
        public string IMixLogFileName { get; set; }
        public string ClientLogFileName { get; set; }

        public int ParsedRows { get; set; } //已解析的行数
        public bool IsOutputExtraExcel { get; set; } //是否输出额外Excel
        public bool IsOutput2Template { get; set; } //是否输出到模板
        public bool IsOutputAbnormalData { get; set; } //是否过滤并输出异常数据
        public bool IsParseDbf { get; set; } //是否解析DBF
        public bool IsParseLog { get; set; } //是否解析日志
        public bool IsHaveBusinBeginTime => BusinBeginTime.Length != 0; //是否启用业务开始时间
        public bool IsHaveBusinEndTime => BusinEndTime.Length != 0; //是否启用业务结束时间
        public bool IsHaveBusinDate => BusinDate.Length != 0; //是否启用业务日期
        public bool IsHaveStableDate => StableDate.Length != 0; //是否启用稳定性日期
        public bool IsHaveNonBusinDate => NonBusinDate.Length != 0; //是否启用非业务日期
        public string BusinBeginTime { get; set; } //业务开始时间
        public string BusinEndTime { get; set; } //业务结束时间
        public string BusinDate { get; set; } //业务验收日期
        public string StableDate { get; set; } //稳定性验收日期
        public string StableDays { get; set; } //稳定性验收天数
        public string NonBusinDate { get; set; } //非业务验收日期
        public string NonBusinDays { get; set; } //非业务验收天数
        public ParseTypes ParseType { get; set; }

        private int TotalDbfData { get; set; }
        private int TotalFilteredData { get; set; }
        private int TotalAbnormalData { get; set; }
        private int TotalParsedData { get; set; }
        private int TotalIgnoreData { get; set; }

        public NewBankIntfParser()
        {
            processBarController = ProcessBarController.GetInstance();
        }

        private void Prepare()
        {
            switch (ParseType)
            {
                case ParseTypes.CMDS:
                    Logger.Log("当前选择的模式是CMDS。");
                    fileListName = "CMDSFileList";
                    OutputSafeFileNameCmds = "Client Check List.xlsx";
                    break;
                case ParseTypes.CSTP:
                    Logger.Log("当前选择的模式是CSTP。");
                    fileListName = "CSTPFileList";
                    OutputSafeFileNameSelf = "本方成交.xlsx";
                    OutputSafeFileNameAffiliates = "关联机构成交.xlsx";
                    break;
                default:
                    Logger.Log("模式不正确。");
                    throw new InvalidOperationException();
            }

            if (IsHaveBusinDate)
            {
                if (IsHaveBusinBeginTime)
                {
                    try
                    {
                        beginBusinDateTime = DateTime.ParseExact($"{BusinDate} {BusinBeginTime}", "yyyyMMdd HH:mm",
                            CultureInfo.CurrentCulture);
                    }
                    catch (FormatException)
                    {
                        Logger.Log("业务开始时间格式非法，时间格式需为[HH:MM]，范围为[00:00~23:59]，请检查！", LogLevel.Error);
                        throw new InvalidOperationException();
                    }
                }
                else
                {
                    try
                    {
                        beginBusinDateTime = DateTime.ParseExact(BusinDate, "yyyyMMdd", CultureInfo.CurrentCulture);
                    }
                    catch (FormatException)
                    {
                        Logger.Log("业务验收日期格式不对，正确格式应为YYYYMMDD。", LogLevel.Error);
                        throw new InvalidOperationException();
                    }
                }

                if (IsHaveBusinEndTime)
                {
                    try
                    {
                        endBusinDateTime = DateTime.ParseExact($"{BusinDate} {BusinEndTime}", "yyyyMMdd HH:mm",
                            CultureInfo.CurrentCulture);
                    }
                    catch (FormatException)
                    {
                        Logger.Log("业务结束时间格式非法，时间格式需为[HH:MM]，范围为[00:00~23:59]，请检查！", LogLevel.Error);
                        throw new InvalidOperationException();
                    }
                }
                else
                {
                    endBusinDateTime = beginBusinDateTime.AddDays(1);
                }

                if (endBusinDateTime < beginBusinDateTime)
                {
                    Logger.Log("业务结束时间不能大于开始时间，请检查！", LogLevel.Error);
                    throw new InvalidOperationException();
                }
            }
        }

        private void test()
        {
            var xml = new XmlDocument();
        }

        private void PrepareForDbf()
        {
            Logger.Log("读取数据字典...");
            dictionary = new IniParser(DictFileName); //IniParser.New(DictFileName);
            test();
            Logger.Log("读取文件列表...");
            fileList = new IniParser(FileListFileName); //IniParser.New(FileListFileName);
            dbfData = new DataSet("DBFData");
            switch (ParseType)
            {
                case ParseTypes.CMDS:
                    break;
                case ParseTypes.CSTP:
                    break;
            }

            configTemplate = new Dictionary<string, IniParser>();
            parsedData = new Dictionary<string, DataSet>();

            ParsedRows = 0;

            LoadTemplate();
            BuildBussinTable();
        }

        /// <summary>
        /// 载入DBF文件到内存表
        /// </summary>
        private void LoadDbf()
        {
            Logger.Log("开始载入DBF文件...");
            foreach (var curFileRow in fileList[fileListName])
            {
                Logger.Log($"正在载入{curFileRow.Key}.dbf...");
                DataTable dataTable;
                try
                {
                    dataTable = Utils.ReadDbfToDataTable($@"{DbfPath}\{BusinDate}{curFileRow.Key}.dbf");
                }
                catch (FileNotFoundException)
                {
                    Logger.Log($@"找不到文件{DbfPath}\{BusinDate}{curFileRow.Key}.dbf，请检查文件是否存在或日期是否填写正确。", LogLevel.Error);
                    throw;
                }
                catch (DirectoryNotFoundException)
                {
                    Logger.Log($"找不到路径{DbfPath}，请检查路径是否存在。", LogLevel.Error);
                    throw;
                }

                dataTable.TableName = curFileRow.Key;
                Logger.Log($"成功载入{dataTable.Rows.Count}条数据。");
                dbfData.Tables.Add(dataTable);

                string curFileName = curFileRow.Key;
                string catalogString = configTemplate[curFileName]["Common"]["Category"]; //文件分类
                if (catalogString != NullString)
                {
                    TotalDbfData += dataTable.Rows.Count;
                }
            }
        }

        private void FilterDbf()
        {
            //StreamWriter streamWriter = new StreamWriter($@"{OutputPath}\异常数据.txt");
            //streamWriter.WriteLine("成交编号\t异常类型");
            foreach (var curFileRow in fileList[fileListName])
            {
                //int filtered = 0;
                string curFileName = curFileRow.Key;
                string catalogString = configTemplate[curFileName]["Common"]["Category"]; //文件分类
                if (catalogString == NullString) continue; //如果文件无分类，则不需要过滤

                if (dbfData.Tables[curFileName].Rows.Count == 0) //如果无数据，则不过滤
                {
                    continue;
                }

                //Logger.Log($"正在过滤{curFileRow}表中的异常数据...");
                Logger.Log($"正在过滤{curFileRow}表中的重复数据...");

                //foreach (DataRow curDataRow in dbfData.Tables[curFileName].Rows)
                processBarController.ProcessCurrent = 0;
                for (int i = 0; i < dbfData.Tables[curFileName].Rows.Count; i++)
                {
                    processBarController.ProcessCurrent =
                        (double) (i + 1) / dbfData.Tables[curFileName].Rows.Count * 100;
                    DataRow curDataRow = dbfData.Tables[curFileName].Rows[i];

                    //过滤重复数据
                    bool duplicateFlag = false;
                    //int row = i;
                    for (int j = 0; j < i; j++)
                    {
                        DataRow curDataRowCheck = dbfData.Tables[curFileName].Rows[j];
                        if (Convert.ToString(curDataRow["ExecID"]) == Convert.ToString(curDataRowCheck["ExecID"]))
                        {
                            Logger.Log($"已删除重复数据：{curDataRowCheck["ExecID"]}。", LogLevel.Warning);
                            curDataRowCheck.Delete();
                            i--;
                            //j--;
                            duplicateFlag = true;
                            TotalFilteredData++;
                        }
                    }
#if !DEBUG
                    //System.Threading.Thread.Sleep(20);
#endif

                    if (duplicateFlag)
                    {
                        continue;
                    }


                    /*
                    bool deleteFlag = false;
                    foreach (var abnormalCol in configTemplate[curFileName]["Abnormal"])
                    {
                        string colValue = GetValue(curFileName, abnormalCol.Key,
                            Convert.ToString(curDataRow[abnormalCol.Key]));
                        var match = Regex.Match(abnormalCol.Value, @"(.*),(.*)\((.*)\)");
                        var groups = match.Groups;
                        string desc = groups[1].Value;
                        string abnormalRelCol = groups[2].Value;
                        string abnormalRelParam = groups[3].Value;

                        var matches = Regex.Matches(abnormalRelParam, @"([^|]*)");

                        foreach (Match param in matches)
                        {
                            GroupCollection paramGroups = param.Groups;
                            //string abnormalType = group[1].Value;
                            string abnormalParam = paramGroups[1].Value;
                            string abnormalDesc = desc;
                            string curExecId = Convert.ToString(curDataRow["ExecID"]);
                            if (Convert.ToString(curDataRow[abnormalRelCol]) == abnormalParam)
                            {
                                if (colValue.Contains("未知类型"))
                                {
                                    abnormalDesc += "枚举值异常";
                                    Logger.Log($"{curExecId} {abnormalDesc}", LogLevel.Warning);
                                    streamWriter.WriteLine($"{curExecId} {abnormalDesc}");
                                    deleteFlag = true;
                                }
                                else if (colValue.Contains("？？？") || colValue.Contains("User-Defined"))
                                {
                                    abnormalDesc += "值异常";
                                    Logger.Log($"{curExecId} {abnormalDesc}", LogLevel.Warning);
                                    streamWriter.WriteLine($"{curExecId} {abnormalDesc}");
                                    deleteFlag = true;
                                }
                                else if (colValue.Contains("<空>") || colValue == "0" || colValue.Length == 0)
                                {
                                    abnormalDesc += "为空";
                                    Logger.Log($"{curExecId} {abnormalDesc}", LogLevel.Warning);
                                    streamWriter.WriteLine($"{curExecId} {abnormalDesc}");
                                    deleteFlag = true;
                                }
                                else if (colValue == "0.00" || colValue == "0.00000000")
                                {
                                    abnormalDesc += "为0";
                                    Logger.Log($"{curExecId} {abnormalDesc}", LogLevel.Warning);
                                    streamWriter.WriteLine($"{curExecId} {abnormalDesc}");
                                    deleteFlag = true;
                                }
                            }
                        }
                    }

                    if (deleteFlag)
                    {
                        curDataRow.Delete();
                        i--;
                        filtered++;
                    }*/
                }

                //Logger.Log($"过滤了{filtered}条数据。");
            }

            //streamWriter.Close();
        }

        private void LoadTemplate()
        {
            foreach (var curFileRow in fileList[fileListName])
            {
                string curFileName = curFileRow.Key;
                Logger.Log($"读取配置文件{curFileName}.ini...");
                configTemplate[curFileName] =
                    new IniParser($@".\Config\{curFileName}.ini"); //IniParser.New($@".\Config\{curFileName}.ini");
            }
        }

        /// <summary>
        /// 创建存放分析后数据的表结构
        /// </summary>
        private void BuildBussinTable()
        {
            Logger.Log("正在生成分析后数据表...");
            //对于每个文件
            foreach (var curFileRow in fileList[fileListName])
            {
                string curFileName = curFileRow.Key; //当前文件名
                parsedData[curFileName] = new DataSet(curFileName); //创建一个新的DataSet

                //对于每个配置文件
                foreach (var curBusin in configTemplate[curFileName].Ini)
                {
                    string curBusinFlag = curBusin.Key; //当前业务类型

                    //如果是业务类型定义则继续，否则忽略
                    try
                    {
                        Convert.ToInt32(Utils.SplitStringByDash(curBusinFlag)[0]);
                    }
                    catch (FormatException)
                    {
                        continue;
                    }

                    parsedData[curFileName].Tables.Add(curBusinFlag); //创建一个新的名字是业务类型的Table，并加入到DataSet

                    //对于当前业务类型下的每一个字段
                    foreach (var curConfigRow in curBusin.Value)
                    {
                        string addCol = curConfigRow.Key; //当前字段
                        parsedData[curFileName].Tables[curBusinFlag].Columns.Add(addCol); //将当前字段加入到业务类型表中
                    }
                }
            }

            Logger.Log("分析后数据表生成完毕。");
        }

        /// <summary>
        /// 分析数据
        /// </summary>
        private void ParseData()
        {
            if (!IsHaveBusinDate)
            {
                Logger.Log("未选择业务日期，跳过解析。", LogLevel.Warning);
                return;
            }

            if (IsHaveBusinBeginTime) Logger.Log($"当前设置的业务验收开始时间是：{beginBusinDateTime}");
            else Logger.Log("当前未设置业务验收开始时间。", LogLevel.Warning);

            if (IsHaveBusinEndTime) Logger.Log($"当前设置的业务验收结束时间是：{endBusinDateTime}");
            else Logger.Log("当前未设置业务验收结束时间。", LogLevel.Warning);


            Logger.Log("开始解析数据...");

            StreamWriter streamWriter = new StreamWriter($@"{OutputPath}\异常数据.txt");
            streamWriter.WriteLine("成交编号\t异常类型");

            //对于每一个文件
            foreach (var curFileRow in fileList[fileListName])
            {
                string curFileName = curFileRow.Key; //当前文件名

                string catalogString = configTemplate[curFileName]["Common"]["Category"]; //文件分类
                if (catalogString == NullString) continue; //如果文件无分类，则不需要解析

                string[] catalog = Utils.SplitStringByDash(catalogString);

                string dataTimeCol = configTemplate[curFileName]["DateTime"].First().Key;
                string dataTimeFormat = configTemplate[curFileName]["DateTime"].First().Value;

                Logger.Log($"开始处理{curFileName}...");
                int max = dbfData.Tables[curFileName].Rows.Count;
                int parsed = 0;
                processBarController.ProcessCurrent = 0;

                //对于该文件每一行数据
                foreach (DataRow curDataRow in dbfData.Tables[curFileName].Rows)
                {
                    bool newDataRowFlag = true;
                    string abnormalString = "";
                    parsed++;
                    //Logger.Log($"正在处理{curFileName}第{parsed}/{max}行");
                    processBarController.ProcessCurrent = (double) parsed / max * 100;
                    //如果不在设定的时间范围内则忽略
                    DateTime curRowDateTime = DateTime.ParseExact(Convert.ToString(curDataRow[dataTimeCol]),
                        dataTimeFormat, CultureInfo.CurrentCulture);
                    if (IsHaveBusinBeginTime && curRowDateTime < beginBusinDateTime) continue;
                    if (IsHaveBusinEndTime && curRowDateTime > endBusinDateTime) continue;

                    string businFlag = curDataRow[catalog[0]] as string; //获取业务标志
                    if (catalog.Length > 1)
                    {
                        for (int i = 1; i < catalog.Length; i++)
                        {
                            businFlag += $"-{curDataRow[catalog[i]]}";
                        }
                    }

                    string newBusinFlag = "";
                    foreach (DataTable dt in parsedData[curFileName].Tables)
                    {
                        string[] oldBusinFlag = Utils.SplitStringByDash(businFlag); //当前行的业务标志
                        string[] testBusinFlag = Utils.SplitStringByDash(dt.TableName); //配置文件中的业务标志

                        //判断业务是否支持
                        for (int i = 0; i < testBusinFlag.Length; i++)
                        {
                            if (oldBusinFlag[i] == testBusinFlag[i])
                            {
                                if (i == 0)
                                {
                                    newBusinFlag = oldBusinFlag[i];
                                }
                                else
                                {
                                    newBusinFlag += $"-{oldBusinFlag[i]}";
                                }
                            }
                            else
                            {
                                newBusinFlag = "";
                                break;
                            }
                        }

                        if (newBusinFlag != "")
                        {
                            break;
                        }

                        //if (testBusinFlag.Length == 1) //业务标志只有1种
                        //{
                        //    if (oldBusinFlag[0] == testBusinFlag[0])
                        //    {
                        //        newBusinFlag = oldBusinFlag[0];
                        //        break;
                        //    }
                        //}
                        //else if (testBusinFlag.Length == 2) //业务标志有2种
                        //{
                        //    if ((oldBusinFlag[0] == testBusinFlag[0]) && (oldBusinFlag[1] == testBusinFlag[1]))
                        //    {
                        //        newBusinFlag = $"{oldBusinFlag[0]}-{oldBusinFlag[1]}";
                        //        break;
                        //    }
                        //}
                        //else if (testBusinFlag.Length == 3) //业务标志有3种
                        //{
                        //    if ((oldBusinFlag[0] == testBusinFlag[0]) && (oldBusinFlag[1] == testBusinFlag[1]) &&
                        //        (oldBusinFlag[2] == testBusinFlag[2]))
                        //    {
                        //        newBusinFlag = $"{oldBusinFlag[0]}-{oldBusinFlag[1]}-{oldBusinFlag[2]}";
                        //        break;
                        //    }
                        //}
                    }

                    if (newBusinFlag == "")
                    {
                        try
                        {
                            Logger.Log(
                                $"发现不支持的业务类型{curFileName}[{catalogString}][{businFlag}]，成交编号[{curDataRow["ExecID"]}]，忽略。",
                                LogLevel.Warning);
                        }
                        catch
                        {
                            Logger.Log(
                                $"发现不支持的业务类型{curFileName}[{catalogString}][{businFlag}]，忽略。",
                                LogLevel.Warning);
                        }

                        TotalIgnoreData++;
                        continue;
                    }

                    businFlag = newBusinFlag;

                    DataRow newDataRow = parsedData[curFileName].Tables[businFlag].NewRow(); //创建新Row

                    bool abnormalFlag = false;

                    //对于当前业务标志，在配置文件中的每一行
                    foreach (var curConfigRow in configTemplate[curFileName][businFlag])
                    {
                        string curOutColKey = curConfigRow.Key; //当前字段
                        var curOutColValues = Utils.SplitStringByString(curConfigRow.Value, "|");
                        string curOutColValue = curOutColValues[0]; //当前字段对应数据表字段
                        string curOutColValueAbnormal = ""; //异常类型对应关系
                        if (curOutColValues.Length > 1)
                        {
                            curOutColValueAbnormal = curOutColValues[1];
                        }

                        string value = ""; //字段值
                        Match match;
                        if (curOutColValue == NullString) //如果当前字段为null，则值为空
                        {
                            value = "";
                        }
                        /*else if ((match = Regex.Match(curOutColValue, @"Relation.(.*)")).Success) //如果是关联字段
                        {
                            var groupCollection = match.Groups;
                            string relName = groupCollection[1].Value;
                            foreach (var curRel in configTemplate[curFileName]["Relation"])
                            {
                                if (curRel.Key == relName)
                                {
                                    Match relMatch = Regex.Match(curRel.Value, RegexRelationString);
                                    if (relMatch.Success)
                                    {
                                        GroupCollection groupCollection2 = relMatch.Groups;
                                        string relDstTableName = groupCollection2[1].Value;
                                        string relDstTableCol = groupCollection2[2].Value;
                                        string relType = groupCollection2[3].Value;
                                        string relSrcCol = groupCollection2[4].Value;
                                        string relSrcSpliter = Regex.Unescape(groupCollection2[5].Value);
                                        string relDstSpliter = Regex.Unescape(groupCollection2[6].Value);

                                        if (relType == "Row")
                                        {
                                            string[] rowNos =
                                                Utils.SplitStringByString(Convert.ToString(curDataRow[relSrcCol]),
                                                    relSrcSpliter);
                                            if (rowNos[0] == "")
                                            {
                                                break;
                                            }

                                            try
                                            {
                                                value += Convert.ToString(dbfData.Tables[relDstTableName]
                                                    .Rows[Convert.ToInt32(rowNos[0]) - 1][relDstTableCol]);
                                                for (int i = 1; i < rowNos.Length; i++)
                                                {
                                                    string addValue = Convert.ToString(dbfData.Tables[relDstTableName]
                                                        .Rows[Convert.ToInt32(rowNos[i]) - 1][relDstTableCol]);
                                                    if (addValue.Length == 0) continue;
                                                    value += relDstSpliter;
                                                    value += addValue;
                                                }
                                            }
                                            catch (IndexOutOfRangeException ex)
                                            {
                                                Logger.Log($"在{relDstTableName}找不到数据，填写空值：{ex.Message}",
                                                    LogLevel.Warning);
                                            }
                                        }
                                    }

                                    break;
                                }
                            }
                        }*/
                        else if ((match = Regex.Match(curOutColValue, @"Relation.(.*)")).Success) //如果是关联字段
                        {
                            var groupCollection = match.Groups;
                            string relName = groupCollection[1].Value;
                            bool foundRel = false;
                            foreach (var curRel in configTemplate[curFileName]["Relation"])
                            {
                                if (curRel.Key == relName)
                                {
                                    foundRel = true;
                                    Match relMatch = Regex.Match(curRel.Value, @"(.*)\((.*)\)");
                                    if (relMatch.Success)
                                    {
                                        GroupCollection relGroups = relMatch.Groups;
                                        string relType = relGroups[1].Value;
                                        string relParam = relGroups[2].Value;

                                        if (relType.Contains("RowSum"))
                                        {
                                            bool ifFlag = relType.Contains("IF");
                                            string regex = ifFlag
                                                ? @"(.*),(.*),""(.*)"",(.*),(.*),(.*),(.*)"
                                                : @"(.*),(.*),""(.*)"",(.*),(.*)";
                                            Match paramMatch = Regex.Match(relParam, regex);
                                            GroupCollection paramGroups = paramMatch.Groups;
                                            string relSrcTableName = paramGroups[1].Value == "this"
                                                ? curFileName
                                                : paramGroups[1].Value;
                                            string relSrcCol = paramGroups[2].Value == "this"
                                                ? relName
                                                : paramGroups[2].Value;
                                            string relSrcSpliter = Regex.Unescape(paramGroups[3].Value);
                                            string relDstTableName = paramGroups[4].Value;
                                            string relDstTableCol = paramGroups[5].Value == "this"
                                                ? relName
                                                : paramGroups[5].Value;

                                            string relIfCol = "";
                                            string relIfValue = "";
                                            if (ifFlag)
                                            {
                                                relIfCol = paramGroups[6].Value;
                                                relIfValue = paramGroups[7].Value;
                                            }

                                            string[] rowNos =
                                                Utils.SplitStringByString(Convert.ToString(curDataRow[relSrcCol]),
                                                    relSrcSpliter);
                                            if (rowNos[0] == "")
                                            {
                                                break;
                                            }

                                            try
                                            {
                                                double addValue = 0;
                                                for (int i = 0; i < rowNos.Length; i++)
                                                {
                                                    if (ifFlag)
                                                    {
                                                        if (Convert.ToString(dbfData.Tables[relDstTableName]
                                                                .Rows[Convert.ToInt32(rowNos[i]) - 1][
                                                                    relIfCol]) != relIfValue)
                                                        {
                                                            continue;
                                                        }
                                                    }

                                                    addValue += Convert.ToDouble(dbfData.Tables[relDstTableName]
                                                        .Rows[Convert.ToInt32(rowNos[i]) - 1][relDstTableCol]);
                                                }

                                                value = Convert.ToString(addValue);
                                            }
                                            catch (IndexOutOfRangeException ex)
                                            {
                                                Logger.Log($"在{relDstTableName}找不到数据，填写空值：{ex.Message}",
                                                    LogLevel.Warning);
                                            }
                                        }
                                        else if (relType.Contains("Row"))
                                        {
                                            bool ifFlag = relType.Contains("IF");
                                            string regex = ifFlag
                                                ? @"(.*),(.*),""(.*)"",(.*),(.*),""(.*)"",(.*),(.*)"
                                                : @"(.*),(.*),""(.*)"",(.*),(.*),""(.*)""";
                                            Match paramMatch = Regex.Match(relParam,
                                                regex);
                                            GroupCollection paramGroups = paramMatch.Groups;
                                            string relSrcTableName = paramGroups[1].Value == "this"
                                                ? curFileName
                                                : paramGroups[1].Value;
                                            string relSrcCol = paramGroups[2].Value == "this"
                                                ? relName
                                                : paramGroups[2].Value;
                                            string relSrcSpliter = Regex.Unescape(paramGroups[3].Value);
                                            string relDstTableName = paramGroups[4].Value;
                                            string relDstTableCol = paramGroups[5].Value == "this"
                                                ? relName
                                                : paramGroups[5].Value;
                                            string relDstSpliter = Regex.Unescape(paramGroups[6].Value);
                                            string relIfCol = "";
                                            string relIfValue = "";
                                            if (ifFlag)
                                            {
                                                relIfCol = paramGroups[7].Value;
                                                relIfValue = paramGroups[8].Value;
                                            }

                                            string[] rowNos =
                                                Utils.SplitStringByString(Convert.ToString(curDataRow[relSrcCol]),
                                                    relSrcSpliter);
                                            if (rowNos[0] == "")
                                            {
                                                break;
                                            }

                                            try
                                            {
                                                //value += Convert.ToString(dbfData.Tables[relDstTableName].Rows[Convert.ToInt32(rowNos[0]) - 1][relDstTableCol]);
                                                for (int i = 0, j = 0; i < rowNos.Length; i++)
                                                {
                                                    if (ifFlag)
                                                    {
                                                        if (Convert.ToString(dbfData.Tables[relDstTableName]
                                                                .Rows[Convert.ToInt32(rowNos[i]) - 1][relIfCol]) !=
                                                            relIfValue)
                                                        {
                                                            continue;
                                                        }
                                                    }

                                                    string addValue = Convert.ToString(dbfData.Tables[relDstTableName]
                                                        .Rows[Convert.ToInt32(rowNos[i]) - 1][relDstTableCol]);
                                                    if (addValue.Length == 0) continue;

                                                    if (j != 0)
                                                    {
                                                        value += relDstSpliter;
                                                    }

                                                    value += addValue;
                                                    j++;
                                                }
                                            }
                                            catch (IndexOutOfRangeException ex)
                                            {
                                                Logger.Log($"在{relDstTableName}找不到数据，填写空值：{ex.Message}",
                                                    LogLevel.Warning);
                                            }
                                        }
                                        else if (relType.Contains("Check"))
                                        {
                                            Match paramMatch = Regex.Match(relParam,
                                                @"(.*),(.*),(.*),(.*),(.*)");
                                            GroupCollection paramGroups = paramMatch.Groups;
                                            string relSrcTableName = paramGroups[1].Value == "this"
                                                ? curFileName
                                                : paramGroups[1].Value;
                                            string relSrcCol = paramGroups[2].Value == "this"
                                                ? relName
                                                : paramGroups[2].Value;
                                            string relCheckType = paramGroups[3].Value;
                                            string relCheckTrueDesc = paramGroups[4].Value;
                                            string relCheckFalseDesc = paramGroups[5].Value;

                                            if (relCheckType == "NotNull")
                                            {
                                                if (Convert.ToString(curDataRow[relSrcCol]) != "")
                                                {
                                                    value = relCheckTrueDesc;
                                                }
                                                else
                                                {
                                                    value = relCheckFalseDesc;
                                                }
                                            }
                                        }
                                        else if (relType.Contains("Spcial"))
                                        {
                                            Match paramMatch = Regex.Match(relParam,
                                                @"(.*),(.*)");
                                            GroupCollection paramGroups = paramMatch.Groups;
                                            string relSrcTableName = paramGroups[1].Value == "this"
                                                ? curFileName
                                                : paramGroups[1].Value;
                                            string relSrcCol = paramGroups[2].Value == "this"
                                                ? relName
                                                : paramGroups[2].Value;
                                            int srcValue = Convert.ToInt32(Convert.ToString(curDataRow[relSrcCol]));
                                            int year = srcValue / 1000000;
                                            int month = srcValue % 1000000 / 1000;
                                            int day = srcValue % 1000;
                                            value =
                                                $"{(year != 0 ? $"{year}Y" : "")}{(month != 0 ? $"{month}M" : "")}{(day != 0 ? $"{day}D" : "")}";
                                        }
                                    }

                                    break;
                                }
                            }

                            if (!foundRel)
                            {
                                Logger.Log($"关联字段配置有问题，请检查配置文件：{curFileName}中找不到关联字段{relName}。");
                                throw new InvalidOperationException();
                            }
                        }
                        else //普通正常字段
                        {
                            try
                            {
                                value = GetValue(curFileName, curOutColValue,
                                    Convert.ToString(curDataRow[curOutColValue]));
                            }

                            catch (ArgumentException)
                            {
                                Logger.Log(
                                    $"在{curFileName}中业务类型[{catalogString}][{businFlag}]未找到字段{curOutColValue},请检查配置文件。",
                                    LogLevel.Error);
                                throw;
                            }
                        }

                        if (IsOutputAbnormalData)
                        {
                            if ((match = Regex.Match(curOutColValueAbnormal, @"Abnormal\((.*)\)")).Success)
                            {
                                var groupCollection = match.Groups;
                                string abnormalTypesString = groupCollection[1].Value;
                                var abnormalTypes = Utils.SplitStringByComma(abnormalTypesString);
                                foreach (var type in abnormalTypes)
                                {
                                    Match abnormalMatch;
                                    string abnormalRegex = configTemplate[curFileName]["AbnormalRegex"][type];
                                    string abnormalDesc = configTemplate[curFileName]["AbnormalDesc"][type];
                                    abnormalMatch = Regex.Match(value, abnormalRegex);
                                    if (abnormalMatch.Success)
                                    {
                                        string curExecId = Convert.ToString(curDataRow["ExecID"]);
                                        abnormalDesc = curOutColKey + abnormalDesc;
                                        if (newDataRowFlag)
                                        {
                                            newDataRowFlag = false;
                                            abnormalFlag = true;
                                            abnormalString = $"{curExecId} {abnormalDesc}";
                                            TotalAbnormalData++;
                                        }
                                        else
                                        {
                                            abnormalString += $" {abnormalDesc}";
                                        }

                                        //Logger.Log($"{curExecId} {abnormalDesc}", LogLevel.Warning);
                                        //streamWriter.WriteLine($"{curExecId} {abnormalDesc}");
                                    }
                                }
                            }
                        }

                        newDataRow[curOutColKey] = value;
                    }

                    if (!abnormalFlag)
                    {
                        parsedData[curFileName].Tables[businFlag].Rows.Add(newDataRow); //插入新行到分析后数据
                        ParsedRows++;
                        TotalParsedData++;
                    }
                    else
                    {
                        Logger.Log($"{abnormalString}", LogLevel.Warning);
                        streamWriter.WriteLine($"{abnormalString}");
                    }

#if !DEBUG
                    //System.Threading.Thread.Sleep(20);
#endif
                }
            }

            streamWriter.Close();
            Logger.Log($"成功解析了{ParsedRows}条数据。");
        }

        private void PrepareForLog()
        {
            if (IsHaveNonBusinDate)
            {
                try
                {
                    beginNonBusinDateTime =
                        DateTime.ParseExact($"{NonBusinDate}", "yyyyMMdd", CultureInfo.CurrentCulture);
                }
                catch (FormatException)
                {
                    Logger.Log("非业务验收日期格式不对，正确格式应为YYYYMMDD。", LogLevel.Error);
                    throw;
                }

                try
                {
                    if (NonBusinDays.Length == 0)
                    {
                        Logger.Log("非业务验收天数未勾选或未设置，使用默认值2天。", LogLevel.Warning);
                        NonBusinDays = "2";
                    }

                    endNonBusinDateTime = beginNonBusinDateTime.AddDays(Convert.ToInt32(NonBusinDays) + 1);
                }
                catch (FormatException)
                {
                    Logger.Log("非业务验收天数格式不对，必须要为数字且大于等于0。", LogLevel.Error);
                    throw;
                }
            }

            if (IsHaveStableDate)
            {
                try
                {
                    beginStableDateTime = DateTime.ParseExact($"{StableDate}", "yyyyMMdd", CultureInfo.CurrentCulture);
                }
                catch (FormatException)
                {
                    Logger.Log("稳定性验收日期格式不对，正确格式应为YYYYMMDD。", LogLevel.Error);
                    throw;
                }

                try
                {
                    if (StableDays.Length == 0)
                    {
                        Logger.Log("稳定性验收天数未勾选或未设置，使用默认值7天。", LogLevel.Warning);
                        StableDays = "7";
                    }

                    endStableDateTime = beginStableDateTime.AddDays(Convert.ToInt32(StableDays) + 1);
                }
                catch (FormatException)
                {
                    Logger.Log("稳定性验收天数格式不对，必须要为数字且大于等于0。", LogLevel.Error);
                    throw;
                }
            }
        }

        private delegate int LogParser(string line);

        private int StableLogParser(string line)
        {
            Match match = Regex.Match(line, @"<(.*?)>(\[.*?\]) (.*?) - (.*)");
            if (match.Success)
            {
                GroupCollection group = match.Groups;
                DateTime dateTime = DateTime.ParseExact(group[1].Value, "yyyy-MM-dd HH:mm:ss,FFF",
                    CultureInfo.CurrentCulture);

                //string logLevel = group[2].Value;
                //string logSource = group[3].Value;
                string logText = group[4].Value;
                if (dateTime >= beginStableDateTime && dateTime <= endStableDateTime)
                {
                    if (logText.Contains("CMDS Error Type")) return 1;
                    if (logText.Contains("market:CMDSCNY>  can't be started")) return 2;
                    if (logText.Contains("market:CMDSCNY>  is started.")) return 1;
                }
                else return 0;
            }

            return 0;
        }

        private int NonBusinLogParser(string line)
        {
            Match match = Regex.Match(line, @"<(.*?)>(\[.*?\]) (.*?) - (.*)");
            if (match.Success)
            {
                GroupCollection group = match.Groups;
                DateTime dateTime = DateTime.ParseExact(group[1].Value, "yyyy-MM-dd HH:mm:ss,FFF",
                    CultureInfo.CurrentCulture);

                //string logLevel = group[2].Value;
                //string logSource = group[3].Value;
                string logText = group[4].Value;
                if (dateTime >= beginNonBusinDateTime && dateTime <= endNonBusinDateTime)
                {
                    if (logText.Contains("CMDS Error Type")) return 1;
                    if (logText.Contains("market:CMDSCNY>  can't be started")) return 2;
                    if (logText.Contains("market:CMDSCNY>  is started")) return 1;
                }
                else return 0;
            }

            return 0;
        }

        private int BusinLogParser(string line)
        {
            Match match = Regex.Match(line, @"(.{12}?)--->\t  (.+)");
            if (match.Success)
            {
                GroupCollection group = match.Groups;
                try
                {
                    DateTime dateTime = DateTime.ParseExact($"{BusinDate} {group[1].Value}", "yyyyMMdd HH:mm:ss:FFF",
                        CultureInfo.CurrentCulture);
                    if (dateTime >= beginBusinDateTime && dateTime <= endBusinDateTime) return 3;
                }
                catch (FormatException)
                {
                    Logger.Log($"日志中有如下时间格式有问题：{group[1].Value}，该行保留", LogLevel.Error);
                    return 3;
                }
            }

            return 0;
        }

        private List<string> LogReader(string filePath)
        {
            StreamReader streamReader = new StreamReader(filePath, Encoding.GetEncoding(936));
            List<string> fileBuffer = new List<string>();

            while (!streamReader.EndOfStream)
            {
                fileBuffer.Add(streamReader.ReadLine());
            }

            Logger.Log($"读取了{fileBuffer.Count}行日志。");

            return fileBuffer;
        }

        private void LogWriter(string outputFileName, List<string> fileBuffer)
        {
            StreamWriter streamWriter =
                new StreamWriter($@"{OutputPath}\{outputFileName}", false, Encoding.GetEncoding(936));
            foreach (var t in fileBuffer)
            {
                streamWriter.WriteLine(t);
            }

            streamWriter.Close();
        }

        private void CmdsLogParser(string filePath, string outputFileName, LogParser parser)
        {
            List<string> parsedLog = new List<string>();
            LinkedList<string> linkedBuffer = new LinkedList<string>();
            List<string> fileBuffer;

            Logger.Log("开始载入日志文件...");
            try
            {
                fileBuffer = LogReader(filePath);
            }
            catch
            {
                Logger.Log($"日志载入失败，请确认文件（{filePath}）是否存在或日期是否填写正确。", LogLevel.Error);
                throw;
            }

            processBarController.ProcessCurrent = 0;
            int parsed = 0;
            int max = fileBuffer.Count;

            Logger.Log("开始解析日志...");
            for (int i = 0; i < max; i++)
            {
                parsed++;
                processBarController.ProcessCurrent = (double) parsed / max * 100;
                string line = fileBuffer[i];

                if (linkedBuffer.Count > 6) linkedBuffer.RemoveFirst();

                int result = parser(line);
                switch (result)
                {
                    case 1: //保留前6行与后2行
                        parsedLog.AddRange(linkedBuffer);
                        linkedBuffer.Clear();
                        parsedLog.Add(line);
                        try
                        {
                            parsedLog.Add(fileBuffer[i + 1]);
                            parsedLog.Add(fileBuffer[i + 2]);
                        }
                        catch
                        {
                            // 忽略
                        }

                        i += 2;
                        parsedLog.Add("");
                        break;
                    case 2:
                        bool found = false;
                        for (int j = i - 5; j <= i + 5; j++)
                        {
                            if (parser(fileBuffer[j]) == 1)
                            {
                                found = true;
                            }
                        }

                        if (!found)
                        {
                            parsedLog.Add(line);
                            parsedLog.Add("");
                        }

                        break;
                    case 3:
                        parsedLog.Add(line);
                        break;
                    default:
                        linkedBuffer.AddLast(line);
                        break;
                }
            }

            LogWriter(outputFileName, parsedLog);
        }

        private void ParseLog()
        {
            if (IsHaveNonBusinDate)
            {
                try
                {
                    Logger.Log("准备开始解析非业务验收日志...");
                    Logger.Log($"设定的非业务验收开始时间为：{beginNonBusinDateTime}");
                    Logger.Log($"设定的非业务验收结束时间为：{endNonBusinDateTime}");
                    CmdsLogParser($@"{ClientLogFileName}", "非业务日志.txt", NonBusinLogParser);
                    Logger.Log($@"非业务验收日志已输出到{OutputPath}\非业务日志.txt。", LogLevel.Warning);
                }
                catch
                {
                    Logger.Log("非业务验收日志解析失败，跳过...", LogLevel.Warning);
                }
            }

            if (IsHaveStableDate)
            {
                try
                {
                    Logger.Log("准备开始解析稳定性验收日志...");
                    Logger.Log($"设定的稳定性验收开始时间为：{beginStableDateTime}");
                    Logger.Log($"设定的稳定性验收结束时间为：{endStableDateTime}");
                    CmdsLogParser($@"{ClientLogFileName}", "稳定性日志.txt", StableLogParser);
                    Logger.Log($@"稳定性验收日志已输出到{OutputPath}\稳定性日志.txt。", LogLevel.Warning);
                }
                catch
                {
                    Logger.Log("稳定性验收日志解析失败，跳过...", LogLevel.Warning);
                }
            }

            if (IsHaveBusinDate)
            {
                try
                {
                    Logger.Log("准备开始解析业务验收日志...");
                    CmdsLogParser($@"{IMixLogFileName}", "业务日志.txt", BusinLogParser);
                    Logger.Log($@"业务验收日志已输出到{OutputPath}\业务日志.txt。", LogLevel.Warning);
                }
                catch
                {
                    Logger.Log("业务验收日志解析失败，跳过...", LogLevel.Warning);
                }
            }
        }

        public void DoParse()
        {
            DateTime dateTime;
            DateTime totalTime = DateTime.Now;
            TimeSpan ts;

            Logger.Log("开始执行...");

            try
            {
                Prepare();
            }
            catch (InvalidOperationException)
            {
                Logger.Log("初始化发生问题，请排查后重试！", LogLevel.Error);
                return;
            }

            if (IsParseDbf && IsHaveBusinDate)
            {
                Logger.Log("初始化DBF解析...");
                try
                {
                    PrepareForDbf();
                }
                catch (InvalidOperationException)
                {
                    Logger.Log("初始化DBF解析发生问题，请排查后重试！", LogLevel.Error);
                    return;
                }

                Logger.Log("准备开始载入DBF文件...");
                dateTime = DateTime.Now;

                try
                {
                    LoadDbf(); //载入DBF
                }
                catch (InvalidOperationException)
                {
                    Logger.Log("载入文件时发生问题，请排查后重试！", LogLevel.Error);
                    return;
                }

                processBarController.ProcessTotal = 25;
                ts = DateTime.Now - dateTime;
                Logger.Log($"载入DBF结束。（耗时{Math.Round(ts.TotalSeconds, 2)}秒）");


                //处理重复数据
                if (IsOutputAbnormalData)
                {
                    Logger.Log("准备开始过滤重复数据...");
                    dateTime = DateTime.Now;
                    FilterDbf();
                    ts = DateTime.Now - dateTime;
                    Logger.Log($"重复数据处理结束。（耗时{Math.Round(ts.TotalSeconds, 2)}秒）");
                }

                Logger.Log("准备开始解析数据...");
                dateTime = DateTime.Now;
                try
                {
                    ParseData(); //解析数据
                }
                catch (InvalidOperationException)
                {
                    Logger.Log("解析数据时发生问题，请排查后重试！", LogLevel.Error);
                    return;
                }

                processBarController.ProcessTotal = 50;
                ts = DateTime.Now - dateTime;
                Logger.Log($"数据解析结束。（耗时{Math.Round(ts.TotalSeconds, 2)}秒）");

                Logger.Log("准备开始输出...");
                dateTime = DateTime.Now;
                try
                {
                    OutputData(); //输出到模板
                }
                catch (InvalidOperationException)
                {
                    Logger.Log("输出时发生问题，请排查后重试！", LogLevel.Error);
                    return;
                }

                ts = DateTime.Now - dateTime;
                Logger.Log($"输出结束。（耗时{Math.Round(ts.TotalSeconds, 2)}秒）");
                Logger.Log($"文件已输出到{OutputPath}。");
            }
            else if (!IsHaveBusinDate)
            {
                Logger.Log("已选择解析DBF，但未勾选或输入业务验收日期，跳过。", LogLevel.Warning);
            }

            processBarController.ProcessTotal = 75;

            if (IsParseLog)
            {
                Logger.Log("初始化日志解析...");
                try
                {
                    PrepareForLog();
                }
                catch (InvalidOperationException)
                {
                    Logger.Log("初始化日志解析发生问题，请排查后重试！", LogLevel.Error);
                    return;
                }

                Logger.Log("准备开始解析日志...");
                dateTime = DateTime.Now;
                try
                {
                    ParseLog();
                }
                catch (InvalidOperationException)
                {
                    Logger.Log("解析日志时发生问题，请排查后重试！", LogLevel.Error);
                    return;
                }

                ts = DateTime.Now - dateTime;
                Logger.Log($"日志解析结束。（耗时{Math.Round(ts.TotalSeconds, 2)}秒）");
            }

            processBarController.ProcessTotal = 100;
            Logger.Log(
                $"DBF数据{TotalDbfData}条，过滤重复数据{TotalFilteredData}条，异常数据{TotalAbnormalData}条，忽略数据{TotalIgnoreData}条，输出数据{TotalParsedData}条。",
                LogLevel.Warning);
            Logger.Log($"执行结束。（总耗时{Math.Round((DateTime.Now - totalTime).TotalSeconds, 2)}秒）");
        }

        private void OutputToExcel(string fileName)
        {
            ExcelOperator xLOperator = new ExcelOperator();
            int max = parsedData[fileName].Tables.Count;
            int parsed = 0;
            processBarController.ProcessCurrent = 0;
            foreach (DataTable curTable in parsedData[fileName].Tables)
            {
                xLOperator.AddSheet(curTable);
                parsed++;
                processBarController.ProcessCurrent = (double) parsed / max * 100;

#if !DEBUG
                //System.Threading.Thread.Sleep(100);
#endif
            }

            xLOperator.SaveAs($@"{OutputPath}\{fileName}");
        }

        /// <summary>
        /// 输出到模板
        /// </summary>
        private void OutputData()
        {
            if (IsOutputExtraExcel)
            {
                Logger.Log("开始输出额外的excel...");
                foreach (var curFileRow in fileList[fileListName])
                {
                    string curFileName = curFileRow.Key;
                    string catalogString = configTemplate[curFileName]["Common"].First().Value;
                    if (catalogString == NullString)
                    {
                        break;
                    }

                    if (IsOutputExtraExcel)
                    {
                        Logger.Log($"正在输出额外的{curFileName}.xlsx");
                        try
                        {
                            OutputToExcel(curFileName);
                        }
                        catch (IOException)
                        {
                            Logger.Log($@"输出时发生问题，请确认文件{OutputPath}\{curFileName}.xlsx未在使用中！", LogLevel.Error);
                            throw;
                        }
                    }

                    Logger.Log($"{curFileName}的数据输出完毕。");
                }
            }

            if (IsOutput2Template)
            {
                ExcelOperator xLOperator;
                switch (ParseType)
                {
                    case ParseTypes.CMDS:
                        xLOperator = new ExcelOperator(TemplateSafeFileNameCmds);
                        Logger.Log("正在插入数据到模板...");
                        xLOperator.InsertData(parsedData, "C", false);
                        Logger.Log("正在写入到模板...");
                        try
                        {
                            xLOperator.SaveAs($@"{OutputPath}\{OutputSafeFileNameCmds}");
                        }
                        catch (IOException)
                        {
                            Logger.Log($@"输出时发生问题，请确认文件{OutputPath}\{OutputSafeFileNameCmds}未在使用中！", LogLevel.Error);
                            throw;
                        }

                        break;
                    case ParseTypes.CSTP:
                        xLOperator = new ExcelOperator(TemplateSafeFileNameSelf);
                        Logger.Log("正在插入数据到模板...");
                        xLOperator.InsertData(parsedData, "A", true);
                        Logger.Log("正在写入到模板...");
                        try
                        {
                            xLOperator.SaveAs($@"{OutputPath}\{OutputSafeFileNameSelf}");
                        }
                        catch (IOException)
                        {
                            Logger.Log($@"输出时发生问题，请确认文件{OutputPath}\{OutputSafeFileNameSelf}未在使用中！", LogLevel.Error);
                            throw;
                        }

                        xLOperator = new ExcelOperator(TemplateSafeFileNameAffiliates);
                        Logger.Log("正在插入数据到模板...");
                        xLOperator.InsertData(parsedData, "A", true);
                        Logger.Log("正在写入到模板...");
                        try
                        {
                            xLOperator.SaveAs($@"{OutputPath}\{OutputSafeFileNameAffiliates}");
                        }
                        catch (IOException)
                        {
                            Logger.Log($@"输出时发生问题，请确认文件{OutputPath}\{OutputSafeFileNameAffiliates}未在使用中！",
                                LogLevel.Error);
                            throw;
                        }

                        break;
                }

                Logger.Log("写入到模板完毕。");
            }
            else
            {
                Logger.Log("未选择输出到模板，跳过。", LogLevel.Warning);
            }
        }

        private string GetValue(string file, string column, string value)
        {
            string dict = configTemplate[file].FindData("Dictionary", column);
            if (dict == null) return value;
            if (value.Length == 0) return "<空>";
            string ret = dictionary.FindData(dict, value);
            if (ret == null) ret = $"<未知类型{value}>";
            return ret;
        }
    }
}